/*
 * Copyright 2015 泛泛o0之辈
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.jfast.framework.base.util;

import cn.jfast.framework.base.cache.SDKProp;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * JFast Api 调用工具
 */
public class ApiCaller {

	private static final String GET = "GET";
	private static final String POST = "POST";
	private static final String PUT = "PUT";
	private static final String DELETE = "DELETE";
	private static final String HEAD = "HEAD";
	private static final String OPTIONS = "OPTIONS";
	private static final String TRACE = "TRACE";

	private static final String CHARSET = SDKProp.encoding;

	private static final SSLSocketFactory sslSocketFactory = getSocketFactory();
	private static final SeftHostnameVerifier trustVerifier = new ApiCaller().new SeftHostnameVerifier();

	private class SeftHostnameVerifier implements HostnameVerifier {
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}

	private class SelfTrustManager implements X509TrustManager {
		public X509Certificate[] getAcceptedIssuers() {
			return null;
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
		}
	}

	private static SSLSocketFactory getSocketFactory() {
		try {
			TrustManager[] tm = { new ApiCaller().new SelfTrustManager() };
			SSLContext sslContext = SSLContext.getInstance("TLS", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			return sslContext.getSocketFactory();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private static HttpURLConnection getHttpConnection(String url,
													   String method, Map<String, String> headers) throws IOException,
			NoSuchAlgorithmException, NoSuchProviderException,
			KeyManagementException {
		URL _url = new URL(url);
		HttpURLConnection httpUrlConnection = (HttpURLConnection) _url.openConnection();
		if (httpUrlConnection instanceof HttpsURLConnection) {
			((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(sslSocketFactory);
			((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(trustVerifier);
		}

		httpUrlConnection.setRequestMethod(method);
		httpUrlConnection.setDoOutput(true);
		httpUrlConnection.setDoInput(true);
		httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");

		if (headers != null && !headers.isEmpty())
			for (Entry<String, String> entry : headers.entrySet())
				httpUrlConnection.setRequestProperty(entry.getKey(), entry.getValue());

		return httpUrlConnection;
	}

	public static Object get(String url) {
		return get(url, null, null);
	}

	public static Object get(String url, Map<String, String> queryParas) {
		return get(url, queryParas, null);
	}

	public static Object get(String url, Map<String, String> queryParas, Map<String, String> headers) {
		return connect(url, queryParas, null, headers, GET);
	}

	public static Object post(String url) {
		return post(url, null, null, null);
	}

	public static Object post(String url,Map<String, String> queryParas) {
		return post(url, queryParas, null,null);
	}

	public static Object post(String url,Object data) {
		return post(url, null, data,null);
	}

	public static Object post(String url, Map<String, String> queryParas,Object data) {
		return post(url, queryParas, data, null);
	}

	public static Object post(String url, Map<String, String> queryParas,
							  Object data, Map<String, String> headers) {
		return connect(url, queryParas, data, headers, POST);
	}

	public static Object put(String url) {
		return put(url, null, null, null);
	}

	public static Object put(String url,Map<String, String> queryParas) {
		return put(url, queryParas, null, null);
	}

	public static Object put(String url, Object data) {
		return put(url, null, data, null);
	}

	public static Object put(String url, Map<String, String> queryParas,Object data) {
		return put(url, queryParas, data, null);
	}

	public static Object put(String url, Map<String, String> queryParas,
							 Object data, Map<String, String> headers) {
		return connect(url, queryParas, data, headers, PUT);
	}

	public static Object delete(String url) {
		return delete(url, null, null);
	}

	public static Object delete(String url, Map<String, String> queryParas) {
		return delete(url, queryParas, null);
	}

	public static Object delete(String url, Map<String, String> queryParas, Map<String, String> headers) {
		return connect(url, queryParas, null, headers, DELETE);
	}

	public static Object trace(String url) {
		return trace(url, null, null);
	}

	public static Object trace(String url, Map<String, String> queryParas) {
		return trace(url, queryParas, null);
	}

	public static Object trace(String url, Map<String, String> queryParas, Map<String, String> headers) {
		return connect(url, queryParas, null, headers, TRACE);
	}

	public static Object head(String url) {
		return head(url, null, null);
	}

	public static Object head(String url, Map<String, String> queryParas) {
		return head(url, queryParas, null);
	}

	public static Object head(String url, Map<String, String> queryParas, Map<String, String> headers) {
		return connect(url, queryParas, null, headers, HEAD);
	}

	public static Object options(String url) {
		return options(url, null, null);
	}

	public static Object options(String url, Map<String, String> queryParas) {
		return options(url, queryParas, null);
	}

	public static Object options(String url, Map<String, String> queryParas, Map<String, String> headers) {
		return connect(url, queryParas, null, headers, OPTIONS);
	}

	private static Object connect(String url, Map<String, String> queryParas,
								 Object data, Map<String, String> headers,String method) {
		HttpURLConnection conn = null;
		try {
			conn = getHttpConnection(buildUrlWithQueryString(url, queryParas),
					method, headers);
			conn.connect();
			if(null != data) {
				ObjectOutputStream objOut = new ObjectOutputStream(conn.getOutputStream());
				objOut.writeObject(data);
				objOut.flush();
				objOut.close();
			}

			return readResponseString(conn);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (conn != null) {
				conn.disconnect();
			}
		}
	}

	private static Object readResponseString(HttpURLConnection conn) {
		Object obj = null;
		ObjectInputStream inputStream = null;
		try {
			if(conn.getInputStream().available() != 0) {
				inputStream = new ObjectInputStream(conn.getInputStream());
				obj = inputStream.readObject();
			}
		} catch (Exception e) {
			obj = null;
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return obj;
	}

	public static String buildUrlWithQueryString(String url,
												 Map<String, String> queryParas) {
		if (queryParas == null || queryParas.isEmpty())
			return url;

		StringBuilder sb = new StringBuilder(url);
		boolean isFirst;
		if (url.indexOf("?") == -1) {
			isFirst = true;
			sb.append("?");
		} else {
			isFirst = false;
		}

		for (Entry<String, String> entry : queryParas.entrySet()) {
			if (isFirst)
				isFirst = false;
			else
				sb.append("&");

			String key = entry.getKey();
			String value = entry.getValue();
			if (!StringUtils.isEmpty(value))
				try {
					value = URLEncoder.encode(value, CHARSET);
				} catch (UnsupportedEncodingException e) {
					throw new RuntimeException(e);
				}
			sb.append(key).append("=").append(value);
		}
		return sb.toString();
	}

}
